package com.atguigu.gmall.product.service.impl;

import com.atguigu.gmall.common.cache.GmallCache;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.rabbit.config.MqConst;
import com.atguigu.gmall.common.rabbit.service.RabbitService;
import com.atguigu.gmall.product.mapper.SkuSaleAttrValueMapper;
import com.atguigu.gmall.product.model.*;
import com.atguigu.gmall.product.service.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
public class SkuManageServiceImpl implements SkuManageService {
    @Autowired
    private SkuInfoService skuInfoService;
    @Autowired
    private SkuImageService skuImageService;
    @Autowired
    private SkuAttrValueService skuAttrValueService;
    @Autowired
    private SkuSaleAttrValueService skuSaleAttrValueService;
    @Autowired
    private BaseCategoryViewService baseCategoryViewService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RabbitService rabbitService;

    /**
     * 新建SkuManageController中提供处理保存SKU方法
     * 新增保存商品SKU信息
     * 1.将提交的商品SKU基本信息封装为SkuInfo对象 存入sku_info表
     * 2.将提交商品SKU图片封装为SkuImage集合 存入sku_image表
     * 3.将提交商品SKu平台属性信息 封装 SkuAttrValue集合 存入sku_attr_value表
     * 4.将提交商品SKu销售属性信息 封装 SkuSaleAttrValue集合 存入sku_sale_attr_value表
     * SkuSaleAttrValue集合,手动多加一个 spu_id
     * @param skuInfo
     * @return
     */
    @Override
    public void getSaveSkuInfo(SkuInfo skuInfo) {
        Long id = skuInfo.getId();
        skuInfoService.save(skuInfo);
        List<SkuImage> skuImageList = skuInfo.getSkuImageList();
        if (!CollectionUtils.isEmpty(skuImageList)){
            skuImageList.stream().forEach(skuImage -> {
                skuImage.setSkuId(id);
            });
            skuImageService.saveBatch(skuImageList);

        }
        List<SkuAttrValue> skuAttrValueList = skuInfo.getSkuAttrValueList();
        if (!CollectionUtils.isEmpty(skuAttrValueList)){
            skuAttrValueList.stream().forEach(skuAttrValue -> {
                skuAttrValue.setSkuId(id);
            });
            skuAttrValueService.saveBatch(skuAttrValueList);
        }
        List<SkuSaleAttrValue> skuSaleAttrValueList = skuInfo.getSkuSaleAttrValueList();
        if(!CollectionUtils.isEmpty(skuSaleAttrValueList)){
            skuSaleAttrValueList.stream().forEach(skuSaleAttrValue -> {
                skuSaleAttrValue.setSkuId(id);
                skuSaleAttrValue.setSpuId(skuInfo.getSpuId());
            });
            skuSaleAttrValueService.saveBatch(skuSaleAttrValueList);
        }
        //5.将新增商品skuid存入布隆过滤器,用于判断商品不存在
        RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(RedisConst.SKU_BLOOM_FILTER);
        bloomFilter.add(id);
    }






    /**
     * 根据分类ID查询商品SKU列表
     *
     * @param infoPage
     * @param category3Id
     * @return
     */
    @Override
    public Page<SkuInfo> getList(Page<SkuInfo> infoPage, Long category3Id) {
        LambdaQueryWrapper<SkuInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SkuInfo::getCategory3Id,category3Id);
        queryWrapper.orderByDesc(SkuInfo::getUpdateTime);
        Page<SkuInfo> page = skuInfoService.page(infoPage, queryWrapper);
        return page;
    }

    /**
     * 上架
     *
     * @param skuId
     * @return
     */
    @Override
    public void getOnSale(Long skuId) {
        SkuInfo skuInfo = new SkuInfo();
        skuInfo.setId(skuId);
        skuInfo.setIsSale(1);
        skuInfoService.updateById(skuInfo);
        // 商品上架成功后,需要同步更新ES索引库;Redis缓存;
        rabbitService.sendMsg(MqConst.EXCHANGE_DIRECT_GOODS,MqConst.ROUTING_GOODS_UPPER,skuId);


    }

    /**
     * 下架   修改 is_sale  即可    工作中尽可能不用is开头做列名
     * onSale  updateById   new 一个 SKUinfo   设置id  和is_sale
     * 或者   LambdaUpdate Query Wrapper,rtod o:后期更新索引库
     *
     * @param skuId
     * @return
     */
    @Override
    public void getCancelSale(Long skuId) {
        LambdaUpdateWrapper<SkuInfo> updateWrapper = new LambdaUpdateWrapper<SkuInfo>();
        updateWrapper.eq(SkuInfo::getId,skuId);
        updateWrapper.set(SkuInfo::getIsSale,0);
        skuInfoService.update(updateWrapper);
        // 商品下架成功后,需要同步更新ES索引库;Redis缓存;
        rabbitService.sendMsg(MqConst.EXCHANGE_DIRECT_GOODS,MqConst.ROUTING_GOODS_LOWER,skuId);

    }

    /**
     * 根据skuId获取SkuInfo
     *根据skuid查询商品sku图片列表 封装 skuinfo对象中
     * @param skuId
     * @return
     */

    /**
     * 获取商品信息 包含商品图片
     *利用缓存获取商品信息,采用分布式锁避(SpringDataRedis)免缓存击穿
     * @param skuId
     * @return
     */
//    @Override
    public SkuInfo getSkuInfoBySpringDataRedis(Long skuId) {
        try {
            //1.优先从分布式缓存Redis中获取数据
            // 1.1 构建缓存业务数据商品信息Rey 形式 sku:商品ID:info
            String dataKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
            //1.2 调用模板对象获取缓存中数据
            SkuInfo skuInfo = (SkuInfo) redisTemplate.opsForValue().get(dataKey);
            //1.3 判断命中结果有值直接返回
            if (skuInfo != null) {
                return skuInfo;
            }
            //1.4判断命中结果没有值，进行第二步
            //2.尝试获取分布式锁
            //2.1 构建锁key sku:商品ID:lock 锁的值:UUID获取锁对象
            String lockKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
//            String lockVal= UUID.randomUUID().toString().replaceAll("-","");
            //2.2 尝试获取锁
            RLock lock = redissonClient.getLock(lockKey);
            boolean tryLock = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX2, RedisConst.SKULOCK_EXPIRE_PX1, TimeUnit.SECONDS);
            if (tryLock) {
                //2.3 获取锁成功执行第三步业务
                //3.执行业务
                try {
                    //3.1 执行查询数据库操作
                    // 3.2 未查询到业务数 将空的结果缓存到redis 存10分钟
                    // 3.3 查询到业务数 将业务结果缓存到redis 存24小时
                    skuInfo = this.getSkuInfoFromDB(skuId);
                    if (skuInfo == null) {
                        redisTemplate.opsForValue().set(dataKey, skuInfo, RedisConst.SKUKEY_TEMPORARY_TIMEOUT, TimeUnit.SECONDS);
                        return skuInfo;
                    } else {
                        redisTemplate.opsForValue().set(dataKey, skuInfo, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                        return skuInfo;
                    }
                } finally {

                    //3.4释放锁
                    lock.unlock();
                }

            } else {
                //2.4 获取锁失败自旋
                TimeUnit.MILLISECONDS.sleep(500);
                return this.getSkuInfoFromDB(skuId);

            }
        } catch (Exception e) {
            return this.getSkuInfoFromDB(skuId);
        }

              }

//        try {
//            //1.优先从分布式缓存Redis中获取商品信息
//            //1.1 构建缓存业务数据Key 形式 sku:1:info
//            String dataKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
//            //1.2 从缓存中获取业务数据
//            SkuInfo skuInfo = (SkuInfo)redisTemplate.opsForValue().get(dataKey);
//            //1.3 如果命中缓冲-则直接响应数据接口
//            if (skuInfo!=null){
//                return skuInfo;
//            }
//            //2.尝试获取分布式锁
//            //2.1 构建某件商品分布式锁Key 形式  sku:1:lock
//            String lockKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
//            //2.2 构建分布式锁值 采用UUID 避免锁互相释放
//            String lockVal = UUID.randomUUID().toString().replaceAll("-","");
//            //2.3 通过set ex nx 命令实现分布式锁
//            Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, RedisConst.SKULOCK_EXPIRE_PX1, TimeUnit.SECONDS);
//            //2.4 如果获取锁成功-进行第3步
//            if (flag){
//                //3.执行业务-记得释放锁
//                try {
//                    //3.1 查询数据库记录
//                    skuInfo = this.getSkuInfoFromDB(skuId);
//                    //3.2 查询数据不存在,将空业务数据放入缓存 缓存 短暂时间 10分钟
//                    if (skuInfo==null){
//                        redisTemplate.opsForValue().set(dataKey,skuInfo,RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);
//                        return  skuInfo;
//                    }else {
//                        //3.3 查询数据存在,将数据放入缓存,真是业务数据存储24h
//                        redisTemplate.opsForValue().set(dataKey,skuInfo,RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);
//                        return  skuInfo;
//                    }
//                } catch (Exception e) {
//                    throw new RuntimeException(e);
//                } finally {
//                    //释放锁,采用lua脚本,保证判断和删除,原子性
////                    redisTemplate.delete(lockKey);
//                    //创建脚本对象 封装lua脚本语言  KEYS[1]:传入锁的名称  ARGV[1]:传入当前线程锁的uuid值
//                    String luaText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
//                            "then\n" +
//                            "    return redis.call(\"del\",KEYS[1])\n" +
//                            "else\n" +
//                            "    return 0\n" +
//                            "end";
//                    DefaultRedisScript<Long> script = new DefaultRedisScript<>();
//                    script.setScriptText(luaText);
//                    script.setResultType(Long.class);
//                    //4.2 为脚本中动态占位参数设置值
//                    //4.3 执行lua脚本
//                    redisTemplate.execute(script, Arrays.asList(lockKey), new String[]{lockVal});
//                }
//
//
//            }else{
//                TimeUnit.MILLISECONDS.sleep(500);
//                return this.getSkuInfo(skuId);
//            }
//        } catch (Exception e) {
//            //如果出现不可预知的错误,,做兜底处理
//            return this.getSkuInfoFromDB(skuId);
//        }
//
//    }
    @Override
    @GmallCache(prefix = "sku:", suffix = ":info")
    public SkuInfo getSkuInfoFromDB(Long skuId) {
        //1.根据主键SKUID查询商品SKU基本信息
        SkuInfo skuInfo = skuInfoService.getById(skuId);
        if (skuInfo != null) {
            //2.根据SkuID查询商品SKu图片列表 封装 SkuInfo对象中
            LambdaQueryWrapper<SkuImage> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(SkuImage::getSkuId, skuId);
            List<SkuImage> skuImageList = skuImageService.list(queryWrapper);
            skuInfo.setSkuImageList(skuImageList);
            return skuInfo;
        }
        return null;
    }


    /**
     * 根据三级分类id获取分类信息
     *
     * @param category3Id
     * @return
     */
    @Override
    public BaseCategoryView getCategoryView(Long category3Id) {
        BaseCategoryView categoryView = baseCategoryViewService.getById(category3Id);
        return categoryView;
    }

    /**
     * 根据skuId 获取最新的商品价格
     *
     * @param skuId
     * @return
     */
    @Override
    public BigDecimal getSkuPrice(Long skuId) {
        LambdaQueryWrapper<SkuInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SkuInfo::getId,skuId);
        queryWrapper.select(SkuInfo::getPrice);
        SkuInfo skuInfo = skuInfoService.getOne(queryWrapper);
        if (skuInfo != null) {
            BigDecimal price = skuInfo.getPrice();
            return price;
        }
       return null;
    }

    /**
     * 根据spuId 获取到销售属性值Id 与skuId 组成的数据集
     * ### 选择顺序切换商品 ${valuesSkuJson}            - 需要新增
     * 选择不同组销售属性,确定skuid
     *
     * @param spuId
     * @return
     */
    @Override
    @GmallCache(prefix = "skuValueIdsMap:")
    public Map<String, Object> getSkuValueIdsMap(Long spuId) {
        Map<String, Object> map=new HashMap<>();
        SkuSaleAttrValueMapper skuSaleAttrValueMapper =(SkuSaleAttrValueMapper) skuSaleAttrValueService.getBaseMapper();
       List< Map<String, Object>> list=skuSaleAttrValueMapper.getSkuValueIdsMap(spuId);
       if (!CollectionUtils.isEmpty(list)){
           //遍历集合 集合每个map,是表中的一条记录
           list.stream().forEach(smap->{
               //获得字段值,放进map中,,返回
               Object skuId = smap.get("sku_id");
               Object valueIds = smap.get("value_ids");
               map.put(valueIds.toString(),skuId);

           });
       }
        return map;
    }


}

